Skip to content

Conversation

@fatih-acar
Copy link
Contributor

@fatih-acar fatih-acar commented Oct 15, 2025

This allows configuring a custom SSL context or cache a default one if not supplied instead of re-creating a default ssl context on each request (default httpx behavior).

Summary by CodeRabbit

  • New Features

    • Support for supplying a custom SSL/TLS context via configuration.
    • Config object is now exposed in the public API for easier setup and customization.
  • Refactor

    • TLS verification behavior unified across sync and async clients for consistent behavior.
  • Bug Fixes

    • TLS configuration errors are surfaced earlier (at client construction) for faster diagnosis.

@coderabbitai
Copy link

coderabbitai bot commented Oct 15, 2025

Walkthrough

BaseClient now loads TLS context early in init and passes ConfigBase.tls_context as the HTTP client's verification parameter (verify=self.config.tls_context) for both async and sync request paths. ConfigBase gained a private _ssl_context, a tls_context property that lazily creates or returns an ssl.SSLContext, and set_ssl_context(context) to set it; Config.clone propagates the SSL context. Tests were added covering TLS context caching, CA-file behavior, tls_insecure handling, and custom TLS context usage. The package public exports now include Config.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.76% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title clearly summarizes the primary change by indicating that the client now supports SSL context caching and configuration, matching the pull request’s objectives of avoiding repeated default context creation and allowing custom contexts.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fac-ssl-context

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9f288e7 and 2623336.

⛔ Files ignored due to path filters (2)
  • tests/unit/sdk/test_data/path-1.pem is excluded by !**/*.pem
  • tests/unit/sdk/test_data/path-2.pem is excluded by !**/*.pem
📒 Files selected for processing (3)
  • infrahub_sdk/client.py (3 hunks)
  • infrahub_sdk/config.py (4 hunks)
  • tests/unit/sdk/test_client.py (2 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

When implementing Infrahub checks, subclass InfrahubCheck and override validate(data); do not implement or rely on a check() method

Files:

  • infrahub_sdk/client.py
  • tests/unit/sdk/test_client.py
  • infrahub_sdk/config.py
infrahub_sdk/client.py

📄 CodeRabbit inference engine (CLAUDE.md)

infrahub_sdk/client.py: Use HTTPX for transport with proxy support (single proxy or HTTP/HTTPS mounts)
Support authentication via API tokens or JWT with automatic refresh

Files:

  • infrahub_sdk/client.py
tests/unit/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Place and write unit tests under tests/unit/ (isolated component tests)

Files:

  • tests/unit/sdk/test_client.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Use the custom pytest plugin (infrahub_sdk.pytest_plugin) fixtures for clients, configuration, and Infrahub-specific testing

Files:

  • tests/unit/sdk/test_client.py
infrahub_sdk/config.py

📄 CodeRabbit inference engine (CLAUDE.md)

infrahub_sdk/config.py: Use a Pydantic-based Config class with environment variable support for configuration
Environment variables for configuration must use the INFRAHUB_ prefix
Validate mutual exclusivity between INFRAHUB_PROXY and {INFRAHUB_PROXY_MOUNTS_HTTP, INFRAHUB_PROXY_MOUNTS_HTTPS}

Files:

  • infrahub_sdk/config.py
🧬 Code graph analysis (2)
infrahub_sdk/client.py (1)
infrahub_sdk/config.py (1)
  • tls_context (139-152)
tests/unit/sdk/test_client.py (1)
infrahub_sdk/config.py (2)
  • tls_context (139-152)
  • set_ssl_context (154-155)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: unit-tests (3.11)
  • GitHub Check: unit-tests (3.12)
  • GitHub Check: unit-tests (3.10)
  • GitHub Check: unit-tests (3.13)
  • GitHub Check: unit-tests (3.9)
  • GitHub Check: integration-tests-latest-infrahub
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (14)
infrahub_sdk/config.py (5)

3-3: LGTM! Imports are appropriate for SSL context support.

The ssl import provides SSLContext types and creation functions, while PrivateAttr enables private attribute support as recommended in past reviews.

Also applies to: 7-7


82-82: LGTM! Private attribute approach addresses past review concerns.

Using PrivateAttr prevents this field from being populated via environment variables and avoids Pydantic serialization issues, as recommended in previous reviews.


138-152: Verify caching behavior with dynamic configuration changes.

The property correctly implements lazy creation and caching. However, once a TLS context is created and cached, subsequent changes to tls_ca_file won't update the cached context (as confirmed by test_verify_config_caches_tls_ca_file_context on line 75-78 of test_client.py). This is likely acceptable for most use cases where configuration is set once at initialization, but consider documenting this behavior to avoid user confusion.

Additionally, note that ssl.create_default_context() can raise exceptions (e.g., if tls_ca_file doesn't exist). These exceptions are surfaced early via self.config.tls_context access in BaseClient.__init__ (line 149 in client.py), which is good for fail-fast behavior.


154-155: LGTM! Setter enables custom SSL context configuration.

The set_ssl_context method provides a clean API for setting custom SSL contexts, as demonstrated in test_verify_config_uses_custom_tls_context.


198-201: LGTM! SSL context is correctly propagated on clone.

The clone method properly transfers the cached SSL context to the new config instance, ensuring consistent TLS behavior across cloned clients.

infrahub_sdk/client.py (3)

149-149: LGTM! Early TLS context loading enables fail-fast error handling.

Accessing self.config.tls_context during initialization surfaces TLS configuration errors (e.g., invalid CA file paths) at client construction time rather than during the first request, providing better user experience.


1028-1028: LGTM! Async client now uses cached TLS context.

Replacing inline verification logic with self.config.tls_context ensures consistent TLS behavior and eliminates redundant SSL context creation on each request.


2752-2752: LGTM! Sync client now uses cached TLS context.

Replacing inline verification logic with self.config.tls_context ensures consistent TLS behavior and eliminates redundant SSL context creation on each request.

tests/unit/sdk/test_client.py (6)

2-3: LGTM! Imports support new TLS context tests.

The ssl and Path imports enable testing SSL context behavior, while adding Config to the public API export improves usability for users who need to configure clients.

Also applies to: 8-8


33-34: LGTM! Constant provides clean test data path reference.

The CURRENT_DIRECTORY constant enables referencing test data files with clear, maintainable paths.


36-52: LGTM! Test thoroughly validates default SSL context caching.

The test correctly verifies that:

  1. The default SSL context is created on first access
  2. Subsequent accesses return the cached instance
  3. create_default_context is called exactly once with no CA file

Good use of monkeypatching to track behavior without filesystem dependencies.


55-81: LGTM! Test validates CA file context caching and immutability.

The test correctly verifies that:

  1. SSL context with CA file is created on first access
  2. Subsequent accesses return the cached instance
  3. Changing tls_ca_file after caching doesn't invalidate the cache

This confirms the intended caching behavior documented in the config.py review.


84-94: LGTM! Test validates insecure TLS mode behavior.

The test correctly verifies that when tls_insecure=True, the tls_context property returns False without attempting to create an SSL context.


97-109: LGTM! Test validates custom SSL context usage.

The test correctly verifies that when a custom SSL context is provided via set_ssl_context(), it's used directly without creating a default context.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Oct 15, 2025

Deploying infrahub-sdk-python with  Cloudflare Pages  Cloudflare Pages

Latest commit: 2623336
Status: ✅  Deploy successful!
Preview URL: https://a7df3bfc.infrahub-sdk-python.pages.dev
Branch Preview URL: https://fac-ssl-context.infrahub-sdk-python.pages.dev

View logs

@codecov
Copy link

codecov bot commented Oct 15, 2025

Codecov Report

❌ Patch coverage is 95.23810% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
infrahub_sdk/config.py 95.00% 0 Missing and 1 partial ⚠️
@@            Coverage Diff             @@
##           stable     #581      +/-   ##
==========================================
+ Coverage   75.69%   75.79%   +0.10%     
==========================================
  Files         100      100              
  Lines        8894     8915      +21     
  Branches     1751     1758       +7     
==========================================
+ Hits         6732     6757      +25     
+ Misses       1680     1677       -3     
+ Partials      482      481       -1     
Flag Coverage Δ
integration-tests 34.69% <42.85%> (+<0.01%) ⬆️
python-3.10 48.32% <66.66%> (+0.11%) ⬆️
python-3.11 48.34% <66.66%> (+0.11%) ⬆️
python-3.12 48.32% <66.66%> (+0.13%) ⬆️
python-3.13 48.32% <66.66%> (+0.11%) ⬆️
python-3.9 46.97% <66.66%> (+0.11%) ⬆️
python-filler-3.12 25.08% <71.42%> (+0.08%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
infrahub_sdk/client.py 67.38% <100.00%> (+0.03%) ⬆️
infrahub_sdk/config.py 89.92% <95.00%> (+0.73%) ⬆️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (5)
tests/unit/sdk/test_client.py (3)

32-48: Extend test coverage to sync client.

The test correctly validates default SSL context caching. However, both InfrahubClient (async) and InfrahubClientSync use the same _get_verify_config method from BaseClient, so consider adding a similar test for the sync client to ensure consistent behavior across both implementations.


51-73: Extend test coverage to sync client.

The test correctly validates CA file-based SSL context caching and cache invalidation when the file path changes. Consider adding similar test coverage for InfrahubClientSync to ensure consistent behavior.


76-86: Add test coverage for custom SSL context and sync client.

The test correctly validates that tls_insecure=True bypasses SSL context creation. However:

  1. Consider adding similar test coverage for InfrahubClientSync.
  2. No test validates that a custom tls_ssl_context provided in the config is actually used by _get_verify_config.

As per learnings: Use the custom pytest plugin (infrahub_sdk.pytest_plugin) fixtures for clients, configuration, and Infrahub-specific testing.

infrahub_sdk/client.py (2)

147-148: Consider thread safety for cache attributes.

The cache attributes _cached_ssl_context and _cached_ssl_context_source are not thread-safe. While this is typically fine for InfrahubClient (async), the InfrahubClientSync could be used in multi-threaded environments where concurrent access to these attributes might cause race conditions (e.g., one thread invalidating the cache while another is reading it).


156-177: Improve error handling for invalid CA files.

The method correctly implements SSL context caching with appropriate priority (custom context → insecure → CA file → default). However, consider:

  1. Error handling: If tls_ca_file points to an invalid or non-existent file, create_default_context(cafile=...) will raise an exception. Consider wrapping this in a try-except to provide a clearer error message.

  2. Defensive checks: The RuntimeError checks on lines 168 and 176 seem unnecessary since create_default_context() typically doesn't return None. If you want to keep them for defensive programming, clarify the rationale in a comment.

  3. Documentation: The priority order isn't documented in the Config class. Consider adding a note in the docstring of tls_ssl_context field explaining when it takes precedence.

Example error handling:

if self.config.tls_ca_file:
    if self._cached_ssl_context is None or self._cached_ssl_context_source != self.config.tls_ca_file:
        try:
            self._cached_ssl_context = create_default_context(cafile=self.config.tls_ca_file)
            self._cached_ssl_context_source = self.config.tls_ca_file
        except (FileNotFoundError, PermissionError) as exc:
            raise RuntimeError(f"Failed to load CA file '{self.config.tls_ca_file}': {exc}") from exc
    if self._cached_ssl_context is None:
        raise RuntimeError("Failed to create SSL context using provided CA file")
    return self._cached_ssl_context
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d8f58b5 and ae4c8af.

📒 Files selected for processing (3)
  • infrahub_sdk/client.py (4 hunks)
  • infrahub_sdk/config.py (2 hunks)
  • tests/unit/sdk/test_client.py (2 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

When implementing Infrahub checks, subclass InfrahubCheck and override validate(data); do not implement or rely on a check() method

Files:

  • infrahub_sdk/client.py
  • infrahub_sdk/config.py
  • tests/unit/sdk/test_client.py
infrahub_sdk/client.py

📄 CodeRabbit inference engine (CLAUDE.md)

infrahub_sdk/client.py: Use HTTPX for transport with proxy support (single proxy or HTTP/HTTPS mounts)
Support authentication via API tokens or JWT with automatic refresh

Files:

  • infrahub_sdk/client.py
infrahub_sdk/config.py

📄 CodeRabbit inference engine (CLAUDE.md)

infrahub_sdk/config.py: Use a Pydantic-based Config class with environment variable support for configuration
Environment variables for configuration must use the INFRAHUB_ prefix
Validate mutual exclusivity between INFRAHUB_PROXY and {INFRAHUB_PROXY_MOUNTS_HTTP, INFRAHUB_PROXY_MOUNTS_HTTPS}

Files:

  • infrahub_sdk/config.py
tests/unit/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Place and write unit tests under tests/unit/ (isolated component tests)

Files:

  • tests/unit/sdk/test_client.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Use the custom pytest plugin (infrahub_sdk.pytest_plugin) fixtures for clients, configuration, and Infrahub-specific testing

Files:

  • tests/unit/sdk/test_client.py
🧠 Learnings (1)
📚 Learning: 2025-08-24T13:35:12.998Z
Learnt from: CR
PR: opsmill/infrahub-sdk-python#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-24T13:35:12.998Z
Learning: Applies to tests/**/*.py : Use the custom pytest plugin (infrahub_sdk.pytest_plugin) fixtures for clients, configuration, and Infrahub-specific testing

Applied to files:

  • tests/unit/sdk/test_client.py
🧬 Code graph analysis (2)
infrahub_sdk/client.py (1)
infrahub_sdk/context.py (1)
  • RequestContext (10-13)
tests/unit/sdk/test_client.py (2)
infrahub_sdk/client.py (16)
  • InfrahubClient (318-1607)
  • get (390-406)
  • get (409-425)
  • get (428-444)
  • get (447-463)
  • get (466-482)
  • get (485-501)
  • get (503-561)
  • get (2107-2123)
  • get (2126-2142)
  • get (2145-2161)
  • get (2164-2180)
  • get (2183-2199)
  • get (2202-2218)
  • get (2220-2278)
  • _get_verify_config (156-177)
tests/unit/sdk/conftest.py (1)
  • client (32-33)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: unit-tests (3.11)
  • GitHub Check: unit-tests (3.12)
  • GitHub Check: unit-tests (3.13)
  • GitHub Check: unit-tests (3.10)
  • GitHub Check: unit-tests (3.9)
  • GitHub Check: integration-tests-latest-infrahub
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (5)
infrahub_sdk/config.py (1)

4-4: LGTM!

The import is correctly added to support the new SSLContext type in the configuration.

tests/unit/sdk/test_client.py (1)

6-6: LGTM!

The import correctly uses the public API by importing Config from infrahub_sdk, aligning with the module's public exports.

infrahub_sdk/client.py (3)

10-10: LGTM!

The imports are correctly added to support SSL context creation and type annotations.


1053-1053: LGTM!

The change correctly uses the centralized _get_verify_config() method for TLS verification, providing consistent behavior and caching across all HTTP requests.


2777-2777: LGTM!

The change correctly uses the centralized _get_verify_config() method, ensuring the sync client has the same TLS verification and caching behavior as the async client.

Copy link
Contributor

@ogenstad ogenstad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some suggestions. With this change I'd also modify it so that we try to load and cache the SSL config when we intialize the config so that an eventual error happens early and not when we first try to use the SDK.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ae4c8af and 4ff80bd.

⛔ Files ignored due to path filters (2)
  • tests/unit/sdk/test_data/path-1.pem is excluded by !**/*.pem
  • tests/unit/sdk/test_data/path-2.pem is excluded by !**/*.pem
📒 Files selected for processing (3)
  • infrahub_sdk/client.py (3 hunks)
  • infrahub_sdk/config.py (4 hunks)
  • tests/unit/sdk/test_client.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • infrahub_sdk/config.py
  • infrahub_sdk/client.py
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

When implementing Infrahub checks, subclass InfrahubCheck and override validate(data); do not implement or rely on a check() method

Files:

  • tests/unit/sdk/test_client.py
tests/unit/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Place and write unit tests under tests/unit/ (isolated component tests)

Files:

  • tests/unit/sdk/test_client.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Use the custom pytest plugin (infrahub_sdk.pytest_plugin) fixtures for clients, configuration, and Infrahub-specific testing

Files:

  • tests/unit/sdk/test_client.py
🧬 Code graph analysis (1)
tests/unit/sdk/test_client.py (2)
infrahub_sdk/config.py (2)
  • Config (164-206)
  • tls_context (139-158)
infrahub_sdk/client.py (14)
  • get (365-381)
  • get (384-400)
  • get (403-419)
  • get (422-438)
  • get (441-457)
  • get (460-476)
  • get (478-536)
  • get (2082-2098)
  • get (2101-2117)
  • get (2120-2136)
  • get (2139-2155)
  • get (2158-2174)
  • get (2177-2193)
  • get (2195-2253)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: integration-tests-latest-infrahub
🔇 Additional comments (5)
tests/unit/sdk/test_client.py (5)

2-2: LGTM!

The import additions are appropriate. Path is needed for the CURRENT_DIRECTORY constant, and Config is now part of the public API as intended by this PR.

Also applies to: 7-7


32-33: LGTM!

Standard pattern for defining the test directory path.


83-94: LGTM!

The test correctly verifies that when tls_insecure=True, no SSL context is created and False is returned for httpx's verify parameter. The use of AssertionError in the fake ensures the test fails if context creation is attempted.


35-52: Monkeypatch target is correct

Config imports ssl directly, so patching ssl.create_default_context works as intended and the caching test requires no changes.


54-81: Clarify TLS context caching behavior when updating tls_ca_file. The test confirms that changing tls_ca_file after first access does not recreate the SSL context, matching the current implementation. If this is intended, document the behavior; otherwise, clear the cached context when the CA file is updated.

This allows configuring a custom SSL context or cache a default one if
not supplied instead of re-creating a default ssl context on each
request (default httpx behavior).

Signed-off-by: Fatih Acar <[email protected]>
@fatih-acar
Copy link
Contributor Author

@ogenstad thank you for the relevant suggestions, it's much better now.

Copy link
Contributor

@ogenstad ogenstad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, I added a suggestion along with a question about when we're returning False for tls_insecure.

Also can you please add a newsfragment?

One change in behaviour could be that when we move to creating the SSL context like we do that the behaviour gets slightly modified with regards to situations when we use the CA file as an environment variable.

Within Infrahub we have this code, it's quite hacky but you see that the parameters to the SSL context is different if we're loading a file cafile vs when we are loading a string from an environment variable cadata.

        tls_ca_path = Path(self.tls_ca_bundle)

        try:
            possibly_file = tls_ca_path.exists()
        except OSError:
            # Raised if the filename is too long which can indicate
            # that the value is a PEM certificate in string form.
            possibly_file = False

        if possibly_file and tls_ca_path.is_file():
            context = ssl.create_default_context(cafile=str(tls_ca_path))
        else:
            context = ssl.create_default_context()
            context.load_verify_locations(cadata=self.tls_ca_bundle)

It might be that HTTPX manages this part differently internally (or it was never tested).

Can you please verify with your branch the behaviour if you do a:

export INFRAHUB_TLS_CA_FILE=PEM-CERT-AS-STRING

I thought we had tests for that in the backend but now see that we don't seem to have that :(

return self._ssl_context

if self.tls_insecure:
return False
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought you mentioned that there was some processing needed even if we just used HTTP to create the context each time? Is it that this doesn't happen if we use HTTP together with tls_insecure=True?

self.group_context: InfrahubGroupContext | InfrahubGroupContextSync
self._initialize()
self._request_context: RequestContext | None = None
_ = self.config.tls_context # Early load of the TLS context to catch errors
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer if this was cached by the config during the creation of the config object and we populated config.__ssl_context at that time. However if we do that I'm not sure that it it would be possible to use the .set_ssl_context() method within the Config.clone, or rather it would be possible but at that time we'd already have created a new SSL Context within the clone method. So I think this might be the best option for now. I guess the only thing to remember is that we'd need to initialize a Client with a config in order to populate the config for reuse elsewhere but I think we always do that.

In order to ensure this stays in place I think we'll want an additional unittest below where you also clone the config and verify that the context is the same object in both of the configs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants